/*
 * This file or a portion of this file is licensed under the terms of
 * the Globus Toolkit Public License, found in file ../GTPL, or at
 * http://www.globus.org/toolkit/download/license.html. This notice must
 * appear in redistributions of this file, with or without modification.
 *
 * Redistributions of this Software, with or without modification, must
 * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
 * some other similar material which is provided with the Software (if
 * any).
 *
 * Copyright 1999-2004 University of Chicago and The University of
 * Southern California. All rights reserved.
 */
package org.griphyn.vdl.euryale;

import org.griphyn.vdl.dax.*;
import org.griphyn.vdl.classes.LFN;
import org.griphyn.vdl.toolkit.Toolkit;
import org.griphyn.vdl.util.Logging;
import org.griphyn.vdl.util.VDLType;

import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.*;
import java.io.*;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

/**
 * This class uses the xerces SAX2 parser to validate and parse an DAX
 * document. This class extends the xerces DefaultHandler so that we
 * only need to override callbacks of interest.
 *
 * @author Kavitha Ranganathan
 * @author Jens-S. Vöckler
 * @author Yong Zhao
 * @version $Revision: 2575 $
 */

public class DAXParser extends DefaultHandler
{
  /**
   * class name of the SAX parser.
   */
  private static String vendorParserClass =
    "org.apache.xerces.parsers.SAXParser";

  /**
   * our own instance of the SAX parser.
   */
  private XMLReader m_parser;

  /**
   * maintain the hierarchy for some debug printing
   */
  private int m_depth;

  /**
   * collects the information for a section 2 job element.
   */
  private Job m_job;

  /**
   * collects the leaves for a profile that is part of a job.
   */
  private Profile m_profile;

  /**
   * maintains the currently viewed section 3 child element.
   */
  private String m_child;

  /**
   * collects the parents associated with a particular child.
   */
  private java.util.List m_parent;

  /**
   * maintains the indications which parent element to be used whenever
   * a filename tag is being encountered.
   */
  private int m_tag;

  // possible values for m_tag
  static final int TAG_ADAG = 0;
  static final int TAG_ARGUMENT = 1;
  static final int TAG_PROFILE = 2;
  static final int TAG_OTHER = 3;

  /**
   * Keep the location within the document
   */
  private Locator m_location;

  /**
   * A Hashmap to forward resolve namespaces that were encountered
   * during parsing.
   */
  private Map m_forward;

  /**
   * A Hashmap to reverse resolve namespaces that were encountered
   * during parsing.
   */
  private Map m_reverse;

  /**
   * Obtain our logger once for multiple uses.
   */
  private Logging m_log;

  /**
   * Maintains the callback class to provide the information to.
   */
  private Callback m_callback;

  /**
   * Sets a feature while capturing failed features right here.
   *
   * @param uri is the feature's URI to modify
   * @param flag is the new value to set.
   * @return true, if the feature could be set, false for an exception
   */
  private boolean set( String uri, boolean flag )
  {
    boolean result = false;
    try {
      this.m_parser.setFeature( uri, flag );
      result = true;
    } catch ( SAXException se ) {
      Logging.instance().log( "default", 0,
			      "Could not set parser feature " +
			      se.getMessage() );
    }
    return result;
  }

  /**
   * The class constructor initializes the Xerces parser, sets the
   * classes that hold the callback functions, and the features that
   * enable schema validation.
   *
   * @param schemaLocation is any URI pointing to the XML schema definition.
   */
  public DAXParser( String schemaLocation )
  {
    // member variables
    this.m_callback = null;
    this.m_child = null;
    this.m_parent = null;

    // parser related members
    this.m_forward = new HashMap();
    this.m_reverse = new HashMap();
    this.m_log = Logging.instance();

    try {
      m_parser = (XMLReader) Class.forName(vendorParserClass).newInstance();
      m_parser.setContentHandler(this);
      // m_parser.setErrorHandler(this);
      m_parser.setErrorHandler( new edu.isi.pegasus.planner.parser.XMLErrorHandler() );

      set( "http://xml.org/sax/features/validation", true );
      set( "http://apache.org/xml/features/validation/dynamic", true );
      set( "http://apache.org/xml/features/validation/schema", true );
      // time+memory consuming, see http://xml.apache.org/xerces2-j/features.html
      // set( "http://apache.org/xml/features/validation/schema-full-checking", true );

      // Send XML Schema element default values via characters().
      set( "http://apache.org/xml/features/validation/schema/element-default", true );
      set( "http://apache.org/xml/features/validation/warn-on-duplicate-attdef", true );
      // mysteriously, this one fails with recent Xerces
      // set( "http://apache.org/xml/features/validation/warn-on-undeclared-elemdef", true );
      set( "http://apache.org/xml/features/warn-on-duplicate-entitydef", true );

      // set the schema default location.
      if ( schemaLocation != null ) {
	setSchemaLocations( ADAG.SCHEMA_NAMESPACE + ' ' + schemaLocation );
	m_log.log("app", 2, "will use " + schemaLocation );
      } else {
	m_log.log("app", 2, "will use document schema hint" );
      }
    } catch (ClassNotFoundException e) {
      m_log.log( "default", 0,
		 "The SAXParser class was not found: " + e);
    } catch (InstantiationException e) {
      m_log.log( "default", 0,
		 "The SAXParser class could not be instantiated: " + e);
    } catch (IllegalAccessException e) {
      m_log.log( "default", 0,
		 "The SAXParser class could not be accessed: " + e);
    }
  }

  /**
   * Obtains the current instance to be used for callbacks.
   *
   * @return the current callback instance object, or null.
   * @see #setCallback( Callback )
   */
  public Callback getCallback()
  { return this.m_callback; }

  /**
   * Sets a new callback object to use for future callbacks.
   *
   * @param callback is the new callback object.
   * @see #getCallback()
   */
  public void setCallback( Callback callback )
  { this.m_callback = callback; }

  /**
   * Set the list of external real locations where the XML schema may be found.
   * Since this list can be determined at run-time through properties etc., we
   * expect this function to be called between instantiating the parser, and
   * using the parser.
   *
   * @param list is a list of strings representing schema locations. The content
   * exists in pairs, one of the namespace URI, one of the location URL.
   */
  public void setSchemaLocations( String list )
  {
    // schema location handling
    try {
      m_parser.setProperty(
	"http://apache.org/xml/properties/schema/external-schemaLocation",
	list );
    } catch ( SAXException se ) {
      m_log.log( "default", 0,
		 "The SAXParser reported an error: " + se );
    }
  }

  /**
   * This function parses a DAX source (could be a document, a stream,
   * etc.), and creates java class instances that correspond to the DAX.
   * These will provided to the callback functions instead of being
   * collected here in memory.
   *
   * @param daxURI is the URI for the DAX source.
   * @return true for valid parsing, false if an error occurred.
   */
  public boolean parse( String daxURI )
  {
    boolean result = false;

    if ( m_callback == null ) {
      m_log.log( "default", 0, "Error: Programmer forgot to provide a callback" );
      return result;
    }

    try {
      InputSource inputSource = new InputSource(daxURI);
      m_parser.parse( inputSource );
      result = true;
    } catch (SAXException e) {
      m_log.log( "default", 0, "SAX Error: " + e );
    } catch (IOException e) {
      m_log.log( "default", 0, "IO Error: " + e );
    }

    return result;
  }

  /**
   * This function parses a DAX source (could be a document, a stream,
   * etc.), and creates java class instances that correspond to the DAX.
   * These will provided to the callback functions instead of being
   * collected here in memory.
   *
   * @param stream is an input stream for the DAX source.
   * @return true for valid parsing, false if an error occurred.
   */
  public boolean parse( InputStream stream )
  {
    boolean result = false;

    if ( m_callback == null ) {
      m_log.log( "default", 0, "Error: Programmer forgot to provide a callback" );
      return result;
    }

    try {
      InputSource inputSource = new InputSource(stream);
      m_parser.parse(inputSource);
      result = true;
    } catch (SAXException e) {
      m_log.log( "default", 0, "SAX Error: " + e );
    } catch (IOException e) {
      m_log.log( "default", 0, "IO Error: " + e );
    }

    return result;
  }

  //
  // here starts the implementation to the Interface
  //

  /**
   * Obtains the document locator from the parser. The document location
   * can be used to print debug information, i.e the current location
   * (line, column) in the document.
   *
   * @param locator is the externally set current position
   */
  public void setDocumentLocator( Locator locator )
  {
    this.m_location = locator;
  }

  /**
   * This method specifies what to do when the parser is at the beginning
   * of the document. In this case, we simply print a message for debugging.
   */
  public void startDocument()
  {
    m_depth = 0;
    m_log.log( "parser", 1, "*** start of document ***" );
  }

  /**
   * The parser comes to the end of the document.
   */
  public void endDocument()
  {
    m_log.log( "parser", 1, "*** end of document ***" );
  }

  /**
   * There is a prefix or namespace defined, put the prefix and its URI
   * in the HashMap. We can get the URI when the prefix is used here after.
   *
   * @param prefix the Namespace prefix being declared.
   * @param uri the Namespace URI the prefix is mapped to.
   */
  public void startPrefixMapping( java.lang.String prefix,
                                  java.lang.String uri )
    throws SAXException
  {
    String p = prefix == null ? null : new String(prefix);
    String u = uri == null ? null : new String(uri);
    m_log.log( "parser", 2, "adding \"" + p + "\" <=> " + u );

    if ( ! this.m_forward.containsKey(p) )
      this.m_forward.put(p, new Stack());
    ((Stack) this.m_forward.get(p)).push(u);

    if ( ! this.m_reverse.containsKey(u) )
      this.m_reverse.put(u, new Stack());
    ((Stack) this.m_reverse.get(u)).push(p);
  }


  /**
   * Out of the reach of the prefix, remove it from the HashMap.
   *
   * @param prefix is the prefix that was being mapped previously.
   */
  public void endPrefixMapping( java.lang.String prefix )
    throws SAXException
  {
    String u = (String) ((Stack) this.m_forward.get(prefix)).pop();
    String p = (String) ((Stack) this.m_reverse.get(u)).pop();
    m_log.log( "parser", 2, "removed \"" + p + "\" <=> " + u );
  }

  /**
   * Helper function to map prefixes correctly onto the elements.
   *
   * @param uri is the parser-returned URI that needs translation.
   * @return the correct prefix for the URI
   */
  private String map( String uri )
  {
    if ( uri == null || uri.length() == 0 ) return "";
    Stack stack = (Stack) this.m_reverse.get(uri);
    String result = stack == null ? null : (String) stack.peek();
    if ( result == null || result.length() == 0 ) return "";
    else return result + ':';
  }


  /**
   * This method defines the action to take when the parser begins to parse
   * an element.
   *
   * @param namespaceURI is the URI of the namespace for the element
   * @param localName is the element name without namespace
   * @param qName is the element name as it appears in the docment
   * @param atts has the names and values of all the attributes
   */
  public void startElement( java.lang.String namespaceURI,
                            java.lang.String localName,
                            java.lang.String qName,
                            Attributes atts )
    throws SAXException
  {

    m_log.log( "parser", 3,
	       "<" + map(namespaceURI) + localName + "> at " +
	       m_location.getLineNumber() + ":" +
	       m_location.getColumnNumber() );

    // yup, one more element level
    m_depth++;

    java.util.List names = new java.util.ArrayList();
    java.util.List values = new java.util.ArrayList();
    for ( int i=0; i < atts.getLength(); ++i ) {
      String name = new String( atts.getLocalName(i) );
      String value = new String( atts.getValue(i) );

      m_log.log( "parser", 2, "attribute " + map(atts.getURI(i)) +
		 name + "=\"" + value + "\"" );
      names.add(name);
      values.add(value);
    }

    createElementObject( localName, names, values );
  }


  /**
   * The parser is at the end of an element. Each successfully and
   * completely parsed Definition will trigger a callback to the
   * registered DefinitionHandler.
   *
   * @param namespaceURI is the URI of the namespace for the element
   * @param localName is the element name without namespace
   * @param qName is the element name as it appears in the docment
   */
  public void endElement( java.lang.String namespaceURI,
                          java.lang.String localName,
                          java.lang.String qName )
    throws SAXException
  {
    // that's it for this level
    m_depth--;
    m_log.log( "parser", 3,
	       "</" + map(namespaceURI) + localName + "> at " +
	       m_location.getLineNumber() + ":" +
	       m_location.getColumnNumber() );

    setElementRelation( localName );
  }

  /**
   * This method is the callback function for characters in an element.
   * The element should be mixed-content.
   *
   * @param ch are the characters from the XML document
   * @param start is the start position into the array
   * @param length is the amount of valid data in the array
   */
  public void characters( char[] ch, int start, int length )
    throws SAXException
  {
    String message = new String( ch, start, length );
    if ( message.length() > 0  ) {
      if ( message.trim().length() == 0 )
	m_log.log( "parser", 3, "Characters: whitespace x " + length );
      else
	m_log.log( "parser", 3, "Characters: \"" + message  + "\"" );
      elementCharacters(message);
    }
  }

  /**
   * Currently, ignorable whitespace will be ignored.
   *
   * @param ch are the characters from the XML document
   * @param start is the start position into the array
   * @param length is the amount of valid data in the array
   */
  public void ignorableWhitespace( char[] ch, int start, int length )
    throws SAXException
  {
    m_log.log( "parser", 3, "Ignoring " + length + " whitespaces" );
  }

  /**
   * Receive a processing instruction. Currently, we are just printing
   * a debug message that we received a PI.
   *
   * @param target the processing instruction target
   * @param data the processing instruction data, or null if none was supplied.
   * The data does not include any whitespace separating it from the target.
   */
  public void processingInstruction( java.lang.String target,
                                     java.lang.String data )
    throws SAXException
  {
    m_log.log( "parser", 2, "processing instruction " + target +
	       "=\"" + data + "\" was skipped!");
  }

  /**
   * Receive a notification that an entity was skipped. Currently, we
   * are just printing a debug message to this fact.
   *
   * @param name The name of the skipped entity. If it is a parameter
   * entity, the name will begin with '%', and if it is the external DTD
   * subset, it will be the string "[dtd]".
   */
  public void skippedEntity(java.lang.String name)
    throws SAXException
  {
    m_log.log( "parser", 2, "entity " + name + " was skipped!");
  }

  //
  // =================================================== our own stuff ===
  //

  /**
   * Small helper method to bundle repetitive parameters in a template
   * for reporting progress.
   *
   * @param subject is the name of the XML element that is being scrutinized.
   * @param name is then name of the element we are working with.
   * @param value is the attribute value.
   */
  private void log( String subject, String name, String value )
  {
    if ( value == null ) value = new String();
    m_log.log( "parser", 3, subject + "." + name + "=\"" +
	       value + "\"" );
  }

  /**
   * Small helper method to bundle repetitive complaints in a template
   * for reporting progress.
   *
   * @param subject is the name of the XML element that is being scrutinized.
   * @param name is then name of the element we are working with.
   * @param value is the attribute value.
   */
  private void complain( String subject, String name, String value )
  {
    if ( value == null ) value = new String();
    m_log.log( "default", 0, "ignoring " + subject + '@' + name +
	       "=\"" + value + '"', true );
  }

  /**
   * This method finds out what is the current element, creates the
   * java object that corresponds to the element, and sets the member
   * variables with the values of the attributes of the element.
   *
   * @param e is the name of the element
   * @param names is a list of attribute names, as strings.
   * @param values is a list of attribute values, to match the key list.
   */
  public void createElementObject( String e,
				   java.util.List names,
				   java.util.List values )
    throws IllegalArgumentException
  {
    // invalid length
    if ( e == null || e.length() < 1 )
      throw new IllegalArgumentException("illegal element length");

    if ( e.equals("adag") ) {
      HashMap cbdata = new HashMap();
      m_tag = TAG_ADAG;

      for ( int i=0; i < names.size(); ++i ) {
	String name = (String) names.get(i);
	String value = (String) values.get(i);

	if ( name.equals("name") ) {
	  this.log( e, name, value );
	  cbdata.put( name, value );
	} else if (name.equals("index")) {
	  this.log( e, name, value );
	  cbdata.put( name, value );
	} else if (name.equals("count")) {
	  this.log( e, name, value );
	  cbdata.put( name, value );
	} else if (name.equals("version")) {
	  this.log( e, name, value );
	  cbdata.put( name, value );
	} else if (name.equals("jobCount")) {
	  this.log( e, name, value );
	  cbdata.put( name, value );
	} else if (name.equals("fileCount")) {
	  this.log( e, name, value );
	  cbdata.put( name, value );
	} else if (name.equals("childCount")) {
	  this.log( e, name, value );
	  cbdata.put( name, value );
	} else if (name.equals("schemaLocation") ) {
	  cbdata.put( name, value );
	} else {
	  this.complain( e, name, value );
	}
      }
      m_callback.cb_document( cbdata );
      return;
    }

    if ( e.equals("filename") || e.equals("stdin") ||
         e.equals("stdout") || e.equals("stderr") ||
	 e.equals("uses") ) {
      Filename fn = new Filename();
      for ( int i=0; i < names.size(); ++i ) {
	String name = (String) names.get(i);
	String value = (String) values.get(i);

	if ( name.equals("file") ) {
	  this.log( e, name, value );
	  fn.setFilename(value);
	} else if (name.equals("link")) {
	  this.log( e, name, value );
	  fn.setLink( VDLType.getLinkType(value) );
	} else if (name.equals("optional")) {
	  this.log( e, name, value );
	  fn.setOptional( new Boolean(value).booleanValue() );
	} else if (name.equals("dontRegister")) {
	  this.log( e, name, value );
	  fn.setDontRegister( new Boolean(value).booleanValue() );
	} 
        //handle the register flag
        else if ( name.equals( "register" ) ){
            fn.setRegister( new Boolean(value).booleanValue() );
        }
        else if (name.equals("dontTransfer")) {
	  // parse tri-state
	  if ( value.equals("false") ) {
	    this.log( e, name, value );
	    fn.setDontTransfer( LFN.XFER_MANDATORY );
	  } else if ( value.equals("true") ) {
	    this.log( e, name, value );
	    fn.setDontTransfer( LFN.XFER_NOT );
	  } else if ( value.equals("optional") ) {
	    this.log( e, name, value );
	    fn.setDontTransfer( LFN.XFER_OPTIONAL );
	  } else {
	    this.complain( e, name, value );
	  }
	} 
        //handle the transfer flag
        else if (name.equals("transfer")) {
	  // parse tri-state
	  if ( value.equals("false") ) {
	    this.log( e, name, value );
	    fn.setTransfer( LFN.XFER_NOT );
	  } else if ( value.equals("true") ) {
	    this.log( e, name, value );
	    fn.setTransfer( LFN.XFER_MANDATORY );
	  } else if ( value.equals("optional") ) {
	    this.log( e, name, value );
	    fn.setTransfer( LFN.XFER_OPTIONAL );
	  } else {
	    this.complain( e, name, value );
	  }
	} 
        else if (name.equals("isTemporary")) {
	  this.log( e, name, value );
	  boolean temp = (new Boolean(value)).booleanValue();
	  fn.setDontRegister( temp );
	  fn.setDontTransfer( temp ? LFN.XFER_NOT : LFN.XFER_MANDATORY );
	} else if (name.equals("temporaryHint")) {
	  this.log( e, name, value );
	  fn.setTemporary( value );
	} else if (name.equals("varname")) {
	  this.log( e, name, value );
	  fn.setVariable( value );
	} else if (name.equals("type") ){
          this.log( e, name, value );
          fn.setType( LFN.typeInt( value ));
	} else {
	  this.complain( e, name, value );
	}
      } // for

      if (e.equals("filename")) {
	switch (m_tag) {
	case TAG_ADAG:
	  m_callback.cb_filename( fn );
	  break;
	case TAG_PROFILE:
	  m_profile.addLeaf(fn);
	  break;
	case TAG_ARGUMENT:
	  m_job.addArgument(fn);
	}
      } else {
	m_tag = TAG_OTHER;

        if ( e.equals("stdin") )
	  m_job.setStdin(fn);
        else if ( e.equals("stdout") )
	  m_job.setStdout(fn);
        else if ( e.equals("stderr") )
	  m_job.setStderr(fn);
        else if ( e.equals("uses") )
	  m_job.addUses(fn);
      }
      return;
    }

    if ( e.equals("job") ) {
      m_job = new Job();
      for ( int i=0; i < names.size(); ++i ) {
	String name = (String) names.get(i);
	String value = (String) values.get(i);

	if ( name.equals("name") ) {
	  this.log( e, name, value );
	  m_job.setName(value);
	} else if ( name.equals("level") ) {
	  this.log( e, name, value );
	  m_job.setLevel( Integer.parseInt(value) );
	} else if (name.equals("namespace")) {
	  this.log( e, name, value );
	  m_job.setNamespace( value );
	} else if (name.equals("version")) {
	  this.log( e, name, value );
	  m_job.setVersion(value);
	} else if (name.equals("compound")) {
	  this.log( e, name, value );
	  m_job.setChain( value );
	} else if (name.equals("id")) {
	  this.log( e, name, value );
	  m_job.setID(value);
	} else if (name.equals("dv-namespace")) {
	  this.log( e, name, value );
	  m_job.setDVNamespace( value );
	} else if (name.equals("dv-name")) {
	  this.log( e, name, value );
	  m_job.setDVName(value);
	} else if (name.equals("dv-version")) {
	  this.log( e, name, value );
	  m_job.setDVVersion(value);
	} else {
	  this.complain( e, name, value );
	}
      }
      return;
    }

    if ( e.equals("child") ) {
      this.m_parent = new ArrayList();
      for ( int i=0; i < names.size(); ++i ) {
	String name = (String) names.get(i);
	String value = (String) values.get(i);

	if ( name.equals("ref") ) {
	  this.log( e, name, value );
	  m_child = value;
	} else {
	  this.complain( e, name, value );
	}
      }
      return;
    }

    if ( e.equals("parent") ) {
      String parent = null;
      for ( int i=0; i < names.size(); ++i ) {
	String name = (String) names.get(i);
	String value = (String) values.get(i);

	if ( name.equals("ref") ) {
	  this.log( e, name, value );
	  parent = value;
	} else {
	  this.complain( e, name, value );
	}
      }
      if ( parent != null ) m_parent.add(parent);
      return;
    }

    if ( e.equals("argument") ){
      m_tag = TAG_ARGUMENT;
      return;
    }

    if ( e.equals("profile") ){
      m_profile = new Profile();
      m_tag = TAG_PROFILE;
      for ( int i=0; i < names.size(); ++i ) {
	String name = (String) names.get(i);
	String value = (String) values.get(i);

	if ( name.equals("namespace") ) {
	  this.log( e, name, value );
	  m_profile.setNamespace(value);
	} else if (name.equals("key")) {
	  this.log( e, name, value );
	  m_profile.setKey( value );
	} else if (name.equals("origin")) {
	  this.log( e, name, value );
	  m_profile.setOrigin( value );
	} else {
	  this.complain( e, name, value );
	}
      }
      return;
    }

    // FIXME: shouldn't this be an exception?
    m_log.log( "filler", 0,
	       "Error: No rules defined for element " + e );
  }

  /**
   * This method sets the relations between the current java object
   * and its parent object according to the element hierarchy.
   * Usually it involves adding the object to the parent's child object
   * list.
   */
  public void setElementRelation(String elementName)
  {
    switch ( elementName.charAt(0) ) {
    case 'a':
      if ( elementName.equals("argument") ) {
	m_tag = TAG_OTHER;
      } else if ( elementName.equals("adag") ) {
	m_callback.cb_done();
      }
      break;

    case 'c':
      if ( elementName.equals("child") ) {
	m_callback.cb_parents( m_child, m_parent );
      }
      break;

    case 'j':
      if ( elementName.equals("job") ) {
	m_tag = TAG_ADAG;
	m_callback.cb_job(m_job);
	m_log.log( "filler", 3, "Adding job " + m_job.getID() );
      }
      break;

    case 'p':
      if ( elementName.equals("profile") ) {
	m_job.addProfile(m_profile);
	m_tag = TAG_OTHER;
      }
      break;

    default:
      // m_log.log( "filler", 0, "Cannot guess parent for " + elementName );
      break;
    }
  }

  /**
   * This method sets the content of the java object corresponding to
   * the element "text", which has mixed content.
   * @see org.griphyn.vdl.classes.Text
   */
  public void elementCharacters(String elementChars)
  {
    PseudoText text = new PseudoText(elementChars);

    switch (m_tag) {
    case TAG_PROFILE:
      m_profile.addLeaf(text);
      this.log( "profile", "text", elementChars);
      break;
    case TAG_ARGUMENT:
      m_job.addArgument(text);
      this.log( "argument", "text", elementChars);
    }
  }
}
